/*!
 * Fast "async" scrypt implementation in JavaScript.
 * Copyright (c) 2013-2016 Dmitry Chestnykh | BSD License
 * https://github.com/dchest/scrypt-async-js
 */

/**
 * scrypt(password, salt, options, callback)
 *
 * where
 *
 * password and salt are strings or arrays of bytes (Array of Uint8Array)
 * options is
 *
 * {
 *    N:      // CPU/memory cost parameter, must be power of two
 *            // (alternatively, you can specify logN)
 *    r:      // block size
 *    p:      // parallelization parameter
 *    dkLen:  // length of derived key, default = 32
 *    encoding: // optional encoding:
 *                    "base64" - standard Base64 encoding
 *                    "hex" — hex encoding,
 *                    "binary" — Uint8Array,
 *                    undefined/null - Array of bytes
 *    interruptStep: // optional, steps to split calculations (default is 0)
 * }
 *
 * Derives a key from password and salt and calls callback
 * with derived key as the only argument.
 *
 * Calculations are interrupted with setImmediate (or zero setTimeout) at the
 * given interruptSteps to avoid freezing the browser. If it's undefined or zero,
 * the callback is called immediately after the calculation, avoiding setImmediate.
 *
 * Legacy way (only supports p = 1) to call this function is:
 *
 * scrypt(password, salt, logN, r, dkLen, [interruptStep], callback, [encoding])
 *
 * In legacy API, if interruptStep is not given, it defaults to 1000.
 * Pass 0 to have callback called immediately.
 *
 */
function scrypt(password, salt, logN, r, dkLen, interruptStep, callback, encoding) {
    'use strict';
  
    function SHA256(m) {
      /** @const */ var K = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b,
        0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01,
        0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7,
        0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
        0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152,
        0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
        0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819,
        0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08,
        0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
        0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
        0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
      ];
  
      var h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a,
          h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19,
          w = new Array(64);
  
      function blocks(p) {
        var off = 0, len = p.length;
        while (len >= 64) {
          var a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7,
              u, i, j, t1, t2;
  
          for (i = 0; i < 16; i++) {
            j = off + i*4;
            w[i] = ((p[j] & 0xff)<<24) | ((p[j+1] & 0xff)<<16) |
                   ((p[j+2] & 0xff)<<8) | (p[j+3] & 0xff);
          }
  
          for (i = 16; i < 64; i++) {
            u = w[i-2];
            t1 = ((u>>>17) | (u<<(32-17))) ^ ((u>>>19) | (u<<(32-19))) ^ (u>>>10);
  
            u = w[i-15];
            t2 = ((u>>>7) | (u<<(32-7))) ^ ((u>>>18) | (u<<(32-18))) ^ (u>>>3);
  
            w[i] = (((t1 + w[i-7]) | 0) + ((t2 + w[i-16]) | 0)) | 0;
          }
  
          for (i = 0; i < 64; i++) {
            t1 = ((((((e>>>6) | (e<<(32-6))) ^ ((e>>>11) | (e<<(32-11))) ^
                 ((e>>>25) | (e<<(32-25)))) + ((e & f) ^ (~e & g))) | 0) +
                 ((h + ((K[i] + w[i]) | 0)) | 0)) | 0;
  
            t2 = ((((a>>>2) | (a<<(32-2))) ^ ((a>>>13) | (a<<(32-13))) ^
                 ((a>>>22) | (a<<(32-22)))) + ((a & b) ^ (a & c) ^ (b & c))) | 0;
  
            h = g;
            g = f;
            f = e;
            e = (d + t1) | 0;
            d = c;
            c = b;
            b = a;
            a = (t1 + t2) | 0;
          }
  
          h0 = (h0 + a) | 0;
          h1 = (h1 + b) | 0;
          h2 = (h2 + c) | 0;
          h3 = (h3 + d) | 0;
          h4 = (h4 + e) | 0;
          h5 = (h5 + f) | 0;
          h6 = (h6 + g) | 0;
          h7 = (h7 + h) | 0;
  
          off += 64;
          len -= 64;
        }
      }
  
      blocks(m);
  
      var i, bytesLeft = m.length % 64,
          bitLenHi = (m.length / 0x20000000) | 0,
          bitLenLo = m.length << 3,
          numZeros = (bytesLeft < 56) ? 56 : 120,
          p = m.slice(m.length - bytesLeft, m.length);
  
      p.push(0x80);
      for (i = bytesLeft + 1; i < numZeros; i++) p.push(0);
      p.push((bitLenHi>>>24) & 0xff);
      p.push((bitLenHi>>>16) & 0xff);
      p.push((bitLenHi>>>8)  & 0xff);
      p.push((bitLenHi>>>0)  & 0xff);
      p.push((bitLenLo>>>24) & 0xff);
      p.push((bitLenLo>>>16) & 0xff);
      p.push((bitLenLo>>>8)  & 0xff);
      p.push((bitLenLo>>>0)  & 0xff);
  
      blocks(p);
  
      return [
        (h0>>>24) & 0xff, (h0>>>16) & 0xff, (h0>>>8) & 0xff, (h0>>>0) & 0xff,
        (h1>>>24) & 0xff, (h1>>>16) & 0xff, (h1>>>8) & 0xff, (h1>>>0) & 0xff,
        (h2>>>24) & 0xff, (h2>>>16) & 0xff, (h2>>>8) & 0xff, (h2>>>0) & 0xff,
        (h3>>>24) & 0xff, (h3>>>16) & 0xff, (h3>>>8) & 0xff, (h3>>>0) & 0xff,
        (h4>>>24) & 0xff, (h4>>>16) & 0xff, (h4>>>8) & 0xff, (h4>>>0) & 0xff,
        (h5>>>24) & 0xff, (h5>>>16) & 0xff, (h5>>>8) & 0xff, (h5>>>0) & 0xff,
        (h6>>>24) & 0xff, (h6>>>16) & 0xff, (h6>>>8) & 0xff, (h6>>>0) & 0xff,
        (h7>>>24) & 0xff, (h7>>>16) & 0xff, (h7>>>8) & 0xff, (h7>>>0) & 0xff
      ];
    }
  
    function PBKDF2_HMAC_SHA256_OneIter(password, salt, dkLen) {
      // compress password if it's longer than hash block length
      if(password.length > 64) {
        // SHA256 expects password to be an Array. If it's not
        // (i.e. it doesn't have .push method), convert it to one.
        password = SHA256(password.push ? password : Array.prototype.slice.call(password, 0));
      }
  
      var i, innerLen = 64 + salt.length + 4,
          inner = new Array(innerLen),
          outerKey = new Array(64),
          dk = [];
  
      // inner = (password ^ ipad) || salt || counter
      for (i = 0; i < 64; i++) inner[i] = 0x36;
      for (i = 0; i < password.length; i++) inner[i] ^= password[i];
      for (i = 0; i < salt.length; i++) inner[64+i] = salt[i];
      for (i = innerLen - 4; i < innerLen; i++) inner[i] = 0;
  
      // outerKey = password ^ opad
      for (i = 0; i < 64; i++) outerKey[i] = 0x5c;
      for (i = 0; i < password.length; i++) outerKey[i] ^= password[i];
  
      // increments counter inside inner
      function incrementCounter() {
        for (var i = innerLen-1; i >= innerLen-4; i--) {
          inner[i]++;
          if (inner[i] <= 0xff) return;
          inner[i] = 0;
        }
      }
  
      // output blocks = SHA256(outerKey || SHA256(inner)) ...
      while (dkLen >= 32) {
        incrementCounter();
        dk = dk.concat(SHA256(outerKey.concat(SHA256(inner))));
        dkLen -= 32;
      }
      if (dkLen > 0) {
        incrementCounter();
        dk = dk.concat(SHA256(outerKey.concat(SHA256(inner))).slice(0, dkLen));
      }
      return dk;
    }
  
    function salsaXOR(tmp, B, bin, bout) {
      var j0  = tmp[0]  ^ B[bin++],
          j1  = tmp[1]  ^ B[bin++],
          j2  = tmp[2]  ^ B[bin++],
          j3  = tmp[3]  ^ B[bin++],
          j4  = tmp[4]  ^ B[bin++],
          j5  = tmp[5]  ^ B[bin++],
          j6  = tmp[6]  ^ B[bin++],
          j7  = tmp[7]  ^ B[bin++],
          j8  = tmp[8]  ^ B[bin++],
          j9  = tmp[9]  ^ B[bin++],
          j10 = tmp[10] ^ B[bin++],
          j11 = tmp[11] ^ B[bin++],
          j12 = tmp[12] ^ B[bin++],
          j13 = tmp[13] ^ B[bin++],
          j14 = tmp[14] ^ B[bin++],
          j15 = tmp[15] ^ B[bin++],
          u, i;
  
      var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7,
          x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14,
          x15 = j15;
  
      for (i = 0; i < 8; i += 2) {
        u =  x0 + x12;   x4 ^= u<<7  | u>>>(32-7);
        u =  x4 +  x0;   x8 ^= u<<9  | u>>>(32-9);
        u =  x8 +  x4;  x12 ^= u<<13 | u>>>(32-13);
        u = x12 +  x8;   x0 ^= u<<18 | u>>>(32-18);
  
        u =  x5 +  x1;   x9 ^= u<<7  | u>>>(32-7);
        u =  x9 +  x5;  x13 ^= u<<9  | u>>>(32-9);
        u = x13 +  x9;   x1 ^= u<<13 | u>>>(32-13);
        u =  x1 + x13;   x5 ^= u<<18 | u>>>(32-18);
  
        u = x10 +  x6;  x14 ^= u<<7  | u>>>(32-7);
        u = x14 + x10;   x2 ^= u<<9  | u>>>(32-9);
        u =  x2 + x14;   x6 ^= u<<13 | u>>>(32-13);
        u =  x6 +  x2;  x10 ^= u<<18 | u>>>(32-18);
  
        u = x15 + x11;   x3 ^= u<<7  | u>>>(32-7);
        u =  x3 + x15;   x7 ^= u<<9  | u>>>(32-9);
        u =  x7 +  x3;  x11 ^= u<<13 | u>>>(32-13);
        u = x11 +  x7;  x15 ^= u<<18 | u>>>(32-18);
  
        u =  x0 +  x3;   x1 ^= u<<7  | u>>>(32-7);
        u =  x1 +  x0;   x2 ^= u<<9  | u>>>(32-9);
        u =  x2 +  x1;   x3 ^= u<<13 | u>>>(32-13);
        u =  x3 +  x2;   x0 ^= u<<18 | u>>>(32-18);
  
        u =  x5 +  x4;   x6 ^= u<<7  | u>>>(32-7);
        u =  x6 +  x5;   x7 ^= u<<9  | u>>>(32-9);
        u =  x7 +  x6;   x4 ^= u<<13 | u>>>(32-13);
        u =  x4 +  x7;   x5 ^= u<<18 | u>>>(32-18);
  
        u = x10 +  x9;  x11 ^= u<<7  | u>>>(32-7);
        u = x11 + x10;   x8 ^= u<<9  | u>>>(32-9);
        u =  x8 + x11;   x9 ^= u<<13 | u>>>(32-13);
        u =  x9 +  x8;  x10 ^= u<<18 | u>>>(32-18);
  
        u = x15 + x14;  x12 ^= u<<7  | u>>>(32-7);
        u = x12 + x15;  x13 ^= u<<9  | u>>>(32-9);
        u = x13 + x12;  x14 ^= u<<13 | u>>>(32-13);
        u = x14 + x13;  x15 ^= u<<18 | u>>>(32-18);
      }
  
      B[bout++] = tmp[0]  = (x0  + j0)  | 0;
      B[bout++] = tmp[1]  = (x1  + j1)  | 0;
      B[bout++] = tmp[2]  = (x2  + j2)  | 0;
      B[bout++] = tmp[3]  = (x3  + j3)  | 0;
      B[bout++] = tmp[4]  = (x4  + j4)  | 0;
      B[bout++] = tmp[5]  = (x5  + j5)  | 0;
      B[bout++] = tmp[6]  = (x6  + j6)  | 0;
      B[bout++] = tmp[7]  = (x7  + j7)  | 0;
      B[bout++] = tmp[8]  = (x8  + j8)  | 0;
      B[bout++] = tmp[9]  = (x9  + j9)  | 0;
      B[bout++] = tmp[10] = (x10 + j10) | 0;
      B[bout++] = tmp[11] = (x11 + j11) | 0;
      B[bout++] = tmp[12] = (x12 + j12) | 0;
      B[bout++] = tmp[13] = (x13 + j13) | 0;
      B[bout++] = tmp[14] = (x14 + j14) | 0;
      B[bout++] = tmp[15] = (x15 + j15) | 0;
    }
  
    function blockCopy(dst, di, src, si, len) {
      while (len--) dst[di++] = src[si++];
    }
  
    function blockXOR(dst, di, src, si, len) {
      while (len--) dst[di++] ^= src[si++];
    }
  
    function blockMix(tmp, B, bin, bout, r) {
      blockCopy(tmp, 0, B, bin + (2*r-1)*16, 16);
      for (var i = 0; i < 2*r; i += 2) {
        salsaXOR(tmp, B, bin + i*16,      bout + i*8);
        salsaXOR(tmp, B, bin + i*16 + 16, bout + i*8 + r*16);
      }
    }
  
    function integerify(B, bi, r) {
      return B[bi+(2*r-1)*16];
    }
  
    function stringToUTF8Bytes(s) {
      var arr = [];
      for (var i = 0; i < s.length; i++) {
        var c = s.charCodeAt(i);
        if (c < 0x80) {
          arr.push(c);
        } else if (c < 0x800) {
          arr.push(0xc0 | c >> 6);
          arr.push(0x80 | c & 0x3f);
        } else if (c < 0xd800) {
          arr.push(0xe0 | c >> 12);
          arr.push(0x80 | (c >> 6) & 0x3f);
          arr.push(0x80 | c & 0x3f);
        } else {
          if (i >= s.length - 1) {
            throw new Error('invalid string');
          }
          i++; // get one more character
          c = (c & 0x3ff) << 10;
          c |= s.charCodeAt(i) & 0x3ff;
          c += 0x10000;
  
          arr.push(0xf0 | c >> 18);
          arr.push(0x80 | (c >> 12) & 0x3f);
          arr.push(0x80 | (c >> 6) & 0x3f);
          arr.push(0x80 | c & 0x3f);
        }
      }
      return arr;
    }
  
    function bytesToHex(p) {
      /** @const */
      var enc = '0123456789abcdef'.split('');
  
      var len = p.length,
          arr = [],
          i = 0;
  
      for (; i < len; i++) {
          arr.push(enc[(p[i]>>>4) & 15]);
          arr.push(enc[(p[i]>>>0) & 15]);
      }
      return arr.join('');
    }
  
    function bytesToBase64(p) {
      /** @const */
      var enc = ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
                '0123456789+/').split('');
  
      var len = p.length,
          arr = [],
          i = 0,
          a, b, c, t;
  
      while (i < len) {
        a = i < len ? p[i++] : 0;
        b = i < len ? p[i++] : 0;
        c = i < len ? p[i++] : 0;
        t = (a << 16) + (b << 8) + c;
        arr.push(enc[(t >>> 3 * 6) & 63]);
        arr.push(enc[(t >>> 2 * 6) & 63]);
        arr.push(enc[(t >>> 1 * 6) & 63]);
        arr.push(enc[(t >>> 0 * 6) & 63]);
      }
      if (len % 3 > 0) {
        arr[arr.length-1] = '=';
        if (len % 3 === 1) arr[arr.length-2] = '=';
      }
      return arr.join('');
    }
  
  
    // Generate key.
  
    var MAX_UINT = (-1)>>>0,
        p = 1;
  
    if (typeof logN === "object") {
      // Called as: scrypt(password, salt, opts, callback)
      if (arguments.length > 4) {
        throw new Error('scrypt: incorrect number of arguments');
      }
  
      var opts = logN;
  
      callback = r;
      logN = opts.logN;
      if (typeof logN === 'undefined') {
        if (typeof opts.N !== 'undefined') {
          if (opts.N < 2 || opts.N > MAX_UINT)
            throw new Error('scrypt: N is out of range');
  
          if ((opts.N & (opts.N - 1)) !== 0)
            throw new Error('scrypt: N is not a power of 2');
  
          logN = Math.log(opts.N) / Math.LN2;
        } else {
          throw new Error('scrypt: missing N parameter');
        }
      }
  
      // XXX: If opts.p or opts.dkLen is 0, it will be set to the default value
      // instead of throwing due to incorrect value. To avoid breaking
      // compatibility, this will only be changed in the next major version.
      p = opts.p || 1;
      r = opts.r;
      dkLen = opts.dkLen || 32;
      interruptStep = opts.interruptStep || 0;
      encoding = opts.encoding;
    }
  
    if (p < 1)
      throw new Error('scrypt: invalid p');
  
    if (r <= 0)
      throw new Error('scrypt: invalid r');
  
    if (logN < 1 || logN > 31)
      throw new Error('scrypt: logN must be between 1 and 31');
  
  
    var N = (1<<logN)>>>0,
        XY, V, B, tmp;
  
    if (r*p >= 1<<30 || r > MAX_UINT/128/p || r > MAX_UINT/256 || N > MAX_UINT/128/r)
      throw new Error('scrypt: parameters are too large');
  
    // Decode strings.
    if (typeof password === 'string')
      password = stringToUTF8Bytes(password);
    if (typeof salt === 'string')
      salt = stringToUTF8Bytes(salt);
  
    if (typeof Int32Array !== 'undefined') {
      //XXX We can use Uint32Array, but Int32Array is faster in Safari.
      XY = new Int32Array(64*r);
      V = new Int32Array(32*N*r);
      tmp = new Int32Array(16);
    } else {
      XY = [];
      V = [];
      tmp = new Array(16);
    }
    B = PBKDF2_HMAC_SHA256_OneIter(password, salt, p*128*r);
  
    var xi = 0, yi = 32 * r;
  
    function smixStart(pos) {
      for (var i = 0; i < 32*r; i++) {
        var j = pos + i*4;
        XY[xi+i] = ((B[j+3] & 0xff)<<24) | ((B[j+2] & 0xff)<<16) |
                   ((B[j+1] & 0xff)<<8)  | ((B[j+0] & 0xff)<<0);
      }
    }
  
    function smixStep1(start, end) {
      for (var i = start; i < end; i += 2) {
        blockCopy(V, i*(32*r), XY, xi, 32*r);
        blockMix(tmp, XY, xi, yi, r);
  
        blockCopy(V, (i+1)*(32*r), XY, yi, 32*r);
        blockMix(tmp, XY, yi, xi, r);
      }
    }
  
    function smixStep2(start, end) {
      for (var i = start; i < end; i += 2) {
        var j = integerify(XY, xi, r) & (N-1);
        blockXOR(XY, xi, V, j*(32*r), 32*r);
        blockMix(tmp, XY, xi, yi, r);
  
        j = integerify(XY, yi, r) & (N-1);
        blockXOR(XY, yi, V, j*(32*r), 32*r);
        blockMix(tmp, XY, yi, xi, r);
      }
    }
  
    function smixFinish(pos) {
      for (var i = 0; i < 32*r; i++) {
        var j = XY[xi+i];
        B[pos + i*4 + 0] = (j>>>0)  & 0xff;
        B[pos + i*4 + 1] = (j>>>8)  & 0xff;
        B[pos + i*4 + 2] = (j>>>16) & 0xff;
        B[pos + i*4 + 3] = (j>>>24) & 0xff;
      }
    }
  
    var nextTick = (typeof setImmediate !== 'undefined') ? setImmediate : setTimeout;
  
    function interruptedFor(start, end, step, fn, donefn) {
      (function performStep() {
        nextTick(function() {
          fn(start, start + step < end ? start + step : end);
          start += step;
          if (start < end)
            performStep();
          else
            donefn();
          });
      })();
    }
  
    function getResult(enc) {
        var result = PBKDF2_HMAC_SHA256_OneIter(password, B, dkLen);
        if (enc === 'base64')
          return bytesToBase64(result);
        else if (enc === 'hex')
          return bytesToHex(result);
        else if (enc === 'binary')
          return new Uint8Array(result);
        else
          return result;
    }
  
    // Blocking variant.
    function calculateSync() {
      for (var i = 0; i < p; i++) {
        smixStart(i*128*r);
        smixStep1(0, N);
        smixStep2(0, N);
        smixFinish(i*128*r);
      }
      callback(getResult(encoding));
    }
  
    // Async variant.
    function calculateAsync(i) {
        smixStart(i*128*r);
        interruptedFor(0, N, interruptStep*2, smixStep1, function() {
          interruptedFor(0, N, interruptStep*2, smixStep2, function () {
            smixFinish(i*128*r);
            if (i + 1 < p) {
              nextTick(function() { calculateAsync(i + 1); });
            } else {
              callback(getResult(encoding));
            }
          });
        });
    }
  
    if (typeof interruptStep === 'function') {
      // Called as: scrypt(...,      callback, [encoding])
      //  shifting: scrypt(..., interruptStep,  callback, [encoding])
      encoding = callback;
      callback = interruptStep;
      interruptStep = 1000;
    }
  
    if (interruptStep <= 0) {
      calculateSync();
    } else {
      calculateAsync(0);
    }
  }
  
  if (typeof module !== 'undefined') module.exports = scrypt;